package builders;

import java.util.ArrayList;

/**
 * @author Vincent
 */

public class VincentAI extends AI {

	public enum Mode{
		NORMAL, HARD
	}

	private static final int OPTIMAL_AI_NORMAL_MOVEMENT = 6;
	private static final int TOTAL_TYPE_OF_RESOURCES = 6;

	private VincentAI.Mode mode;
	private ArrayList<Building> availableBuilding;

	/**
	 * @param name		specify the name of the AI
	 * @param homebase	specify the location of the AI's homeBase
	 * @param mode		specify the mode of difficulty of the AI
	 */
	public VincentAI(String name, int homebase, VincentAI.Mode mode) {
		super(name, homebase);
		this.mode = mode;
	}

	/**
	 * @return	the mode of difficulty of the AI
	 */
	public VincentAI.Mode getPlayerAIMode(){
		return mode;
	}

	@Override
	public Building selectBuilding(GameState gameState) {
		sortBuilding(gameState);
		return chooseBuildingToBuy(gameState, mode);
	}

	@Override
	public int selectTile(GameState gameState) {
		sortBuilding(gameState);
		return makeMove(gameState, mode);
	}

	/**
	 * The available buildings are arranged in the order of most points first.
	 * 
	 * @param gameState	specify the gameState from which the BuildingManager will be from 
	 */
	private void sortBuilding(GameState gameState){
		Building[] manager = gameState.getBuildingManager().getManager();
		availableBuilding = new ArrayList<Building>();

		for (Building buildingFromManager : manager){
			if (buildingFromManager.getNumberOfBuildingsAvailable() > 0){	
				availableBuilding.add(buildingFromManager);
			}
		}

		availableBuilding = sortByBuildingPoints(availableBuilding);
	}

	/**
	 * @param unsortedAvailableBuilding	specify the unsorted Building list
	 * @return							the sorted Building list by Building's points
	 */
	private ArrayList<Building> sortByBuildingPoints(ArrayList<Building> unsortedAvailableBuilding) {
		Building building = null;
		int numberOfTypeOfSortedBuilding = 0;
		ArrayList<Building> sortedBuilding = new ArrayList<Building>();
		int numberOfTypeOfUnsortedBuilding = unsortedAvailableBuilding.size();

		while (numberOfTypeOfSortedBuilding < numberOfTypeOfUnsortedBuilding){
			int buildingPoints = 0;

			for (Building unsortedBuilding : unsortedAvailableBuilding){
				if (buildingPoints == 0 || buildingPoints < unsortedBuilding.getPoints()){
					buildingPoints = unsortedBuilding.getPoints();
					building = unsortedBuilding;
				}
			}

			sortedBuilding.add(building);
			unsortedAvailableBuilding.remove(building);
			numberOfTypeOfSortedBuilding++;
		}
		return sortedBuilding;
	}

	/**
	 * @param gameState	specify the gameState which the AI will use to make its decision
	 * @param mode		specify the mode of difficulty of the AI
	 * @return			the number of Activity Points the AI decided to use
	 */
	private int makeMove(GameState gameState, VincentAI.Mode mode){
		switch (mode) {
		case NORMAL:
			return selectTileNormal(gameState);
		default: return selectTileHard(gameState); //hard mode
		}
	}

	/**
	 * @param gameState	specify the gameState which the AI will use to make its decision
	 * @param mode		specify the mode of difficulty of the AI
	 * @return			the name of the building which the AI decided to buy
	 * 					Note: the return value can be null
	 * 					-> this means that the AI is unable to buy any building
	 */
	private Building chooseBuildingToBuy(GameState gameState, VincentAI.Mode mode){
		return buyBuilding(gameState);
	}

	/**
	 * The AI will buy the building that gives the most points if possible.
	 * 
	 * @param gameState	specify the gameState which the AI will use to make its decision
	 * @return			the name of the building which the AI decided to buy
	 *					Note: the return value can be null
	 * 					-> this means that the AI is unable to buy any building
	 */
	private Building buyBuilding(GameState gameState) {

		Building possibleBuilding = null;

		for (Building building : availableBuilding){

			int[] lackedResourcesListAI = new int[TOTAL_TYPE_OF_RESOURCES];

			BuildingRequirement[] buildingRequirement = building.getBuildingRequirementList();

			// to generate the required resources that the AI is lacking to buy a building
			lackedResourcesListAI = updateLackedResourceList(lackedResourcesListAI,
					buildingRequirement);

			if (notLackingResources(lackedResourcesListAI)){
				// AI has enough resources to buy this building
				if (possibleBuilding == null){
					possibleBuilding = building;
				} else if (possibleBuilding.getPoints() < building.getPoints()){
					possibleBuilding = building;	
				}
			}

		}

		if (possibleBuilding == null){
			// AI is unable to buy any building
			return null;
		}
		return possibleBuilding;
	}

	/**
	 * The AI will check if the AI is lacking resources for any building.
	 * If the AI is lacking resources, the AI will move to a tile that will give the most of any lacking resources.
	 * Otherwise, the AI will move the most number of tiles, provided that tile is not a homeBase.
	 * (If it is a homeBase, the AI will move to the previous tile.
	 * 
	 * @param gameState	specify the gameState which the AI will use to make its decision
	 * @return			the tile number the AI decided to use 
	 */
	private int selectTileHard(GameState gameState) {

		ArrayList<Integer> tilesYouCanMoveTo = gameState.getTilesYouCanMoveTo();
		int movementPoints = tilesYouCanMoveTo.size();
		TileManager tileManager = gameState.getTileManager();
		Tile[] tiles = tileManager.getTiles();
		int newPiecePosition = 0;

		for (Building building : availableBuilding){

			int[] lackedResourcesListAI = new int[TOTAL_TYPE_OF_RESOURCES];
			int possibleAmount = 0;
			int amount = 0;

			BuildingRequirement[] buildingRequirement = building.getBuildingRequirementList();

			// to generate the required resources that the AI is lacking to buy a building
			lackedResourcesListAI = updateLackedResourceList(lackedResourcesListAI,
					buildingRequirement);

			for (int i = 0; i < tilesYouCanMoveTo.size() - 1; i ++){
				int possiblePiecePosition = tilesYouCanMoveTo.get(i);
				int actionPointsLeft = tilesYouCanMoveTo.size() - i;

				if (tiles[possiblePiecePosition].getIsHomeBase() && getHomeBase() != possiblePiecePosition){
					break;
				}

				TileResult tileResult = tiles[possiblePiecePosition].execute(actionPointsLeft); 
				possibleAmount = tileResult.getAmountToIncrease();

				if (conditionsToMeetInOrderToChooseThisMove(lackedResourcesListAI, tileResult, amount, possibleAmount)){
					amount = possibleAmount;
					newPiecePosition = possiblePiecePosition;
				}

			} // end of checking each tile the AI can move to

			// if AI has a possible move, return the new tile position
			if (newPiecePosition != 0){
				return newPiecePosition;
			}

		} //end of checking of each building

		int furthestPossiblePiecePosition = tilesYouCanMoveTo.get(movementPoints - 1);

		// if the furthest possible piece position is a homeBase and also not the AI's homeBase 
		if (tiles[furthestPossiblePiecePosition].getIsHomeBase() && getHomeBase() != furthestPossiblePiecePosition){
			if (movementPoints != 1){
				return tilesYouCanMoveTo.get(movementPoints - 2);	
			}
		}
		return tilesYouCanMoveTo.get(movementPoints - 1);
	}

	/*
	 *  if the resource from the tile is the resource the AI is lacking
	 *  AND
	 *  if the amount is 0 (when the AI has not decided to make the move), OR the resource from the tile received is more than before
	 */
	private boolean conditionsToMeetInOrderToChooseThisMove(int[] lackedResourcesList, TileResult tileResult, int amount, int possibleAmount){
		return lackedResourcesList[tileResult.getResourceThatIsAffected().ordinal()] > 0 &&
		(amount == 0 || amount < possibleAmount);
	}

	/**
	 * This selection is done by allowing the AI to move up to a max of 7 tiles. 
	 * When the AI is able to move more than 7 tiles, the AI will still stop at 7th tiles.
	 * This is to enable the AI to collect more resources.
	 * 
	 * @param gameState	specify the gameState which the AI will use to make its decision
	 * @return 			7 if MovementActivityPoints is more than 6, else return MovementActivityPoints
	 */
	private int selectTileNormal(GameState gameState) {
		ArrayList<Integer> tilesYouCanMoveTo = gameState.getTilesYouCanMoveTo();
		int possibleMovement = tilesYouCanMoveTo.size();

		if (possibleMovement > OPTIMAL_AI_NORMAL_MOVEMENT){
			return tilesYouCanMoveTo.get(OPTIMAL_AI_NORMAL_MOVEMENT);
		} else {
			return tilesYouCanMoveTo.get(possibleMovement - 1);
		}
	}

	/**
	 * @param lackedResourcesList			specify the old lackedResourcesList
	 * @param buildingRequirement			specify the resources that is needed
	 * @return								the updated lackedResourcesList
	 */
	private int[] updateLackedResourceList(int[] lackedResourcesList,
			BuildingRequirement[] buildingRequirement) {

		for (Player.Resource resource : Player.Resource.values()){
			lackedResourcesList[resource.ordinal()] -= getCurrentResourceAmount(resource);	
		}

		for (BuildingRequirement requirement : buildingRequirement){
			Player.Resource resourceRequirement = requirement.getResources();
			lackedResourcesList[resourceRequirement.ordinal()] += requirement.getAmount();
		}
		return lackedResourcesList;
	}

	/**
	 * @param lackedResourcesList	specify the resource list to check
	 * @return						true if there is no resources lacking, other return false if the player is in need of resources
	 */
	private boolean notLackingResources(int[] lackedResourcesList) {
		for (int resourceAmount : lackedResourcesList){
			if (resourceAmount > 0){
				return false;
			}
		}
		return true;
	}

	/**
	 * This method should only be called by VincentAIATD.
	 * 
	 * @param unsortedAvailableBuilding	specify the unsorted buildings
	 * @return							a ArrayList<Building> of buildings sorted by their points
	 */
	public ArrayList<Building> sortByBuildingPointsForATD(ArrayList<Building> unsortedAvailableBuilding){
		return sortByBuildingPoints(unsortedAvailableBuilding);
	}
}

